package datetime

import (
	"errors"
	"strconv"
	"time"

	e "gitee.com/yrwy/msgo/pkg/errors"
)

// 模板时间：golang里面只能是 "2006-01-02 15:04:05" （go的诞生时间）
const Layout = "2006-01-02 15:04:05"

type Date uint32     //20060102
type Time uint32     //15*3600+4*60+5
type Datetime uint64 //20060102150405
type MTime uint64    //15*3600000+4*60000+5*1000+ms

func MakeDate(y, m, d uint) Date {
	return Date(y*1e4 + m*100 + d)
}

func MakeTime(h, m, s uint) Time {
	return Time(h*3600 + m*60 + s)
}

func MakeDatetime(y, m, d, H, M, S uint) Datetime {
	return Datetime(uint64(y)*1e10 + uint64(m)*1e8 + uint64(d)*1e6 + uint64(H)*1e4 + uint64(M)*100 + uint64(S))
}

func MakeMTime(h, m, s, ms uint) MTime {
	return MTime(uint64(h*3600000) + uint64(m*60000) + uint64(s*1000+ms))
}

func NowDate() Date {
	return SysTime2Date(time.Now())
}

func NowTime() Time {
	t := time.Now()
	return MakeTime(uint(t.Hour()), uint(t.Minute()), uint(t.Second()))
}

func NowDatetime() Datetime {
	return SysTime2Datetime(time.Now())
}

func (dt Date) ToSysTime() time.Time {
	return time.Date(int(dt/1e4), time.Month((dt%1e4)/100), int((dt % 100)), 0, 0, 0, 0, time.Local)
}

func (dt Date) AddDays(n int) Date {
	t := dt.ToSysTime()
	t = t.Add(time.Hour * 24 * time.Duration(n))
	return SysTime2Date(t)
}

func (dt Datetime) ToSysTime() time.Time {
	return time.Date(int(dt/1e10), time.Month((dt%1e10)/1e8), int((dt%1e8)/1e6), int((dt%1e6)/1e4), int((dt%1e4)/100), int(dt%100), 0, time.Local)
}

func (dt Datetime) AddSeconds(n int) Datetime {
	t := dt.ToSysTime()
	t = t.Add(time.Duration(n) * time.Second)
	return SysTime2Datetime(t)
}

func (dt Datetime) AddMinutes(n int) Datetime {
	return dt.AddSeconds(n * 60)
}

func (dt Datetime) AddHours(n int) Datetime {
	return dt.AddSeconds(n * 3600)
}

func (dt Datetime) AddDays(n int) Datetime {
	return dt.AddSeconds(n * 86400)
}

func SysTime2Date(t time.Time) Date {
	return MakeDate(uint(t.Year()), uint(t.Month()), uint(t.Day()))
}

func SysTime2Datetime(t time.Time) Datetime {
	return MakeDatetime(uint(t.Year()), uint(t.Month()), uint(t.Day()), uint(t.Hour()), uint(t.Minute()), uint(t.Second()))
}

type dataTimeStr []rune

func (x *dataTimeStr) getStringValue(limit int) int {
	dt := *x
	r := 0
	i := 0
	for i < len(dt) {
		c := dt[i]
		i++
		if c >= '0' && c <= '9' {
			r = r*10 + int(c-'0')
		} else {
			if i == 1 {
				return -1
			}
			break
		}
		if limit > 0 && i > limit {
			return -1
		}
	}
	*x = dt[i:]
	return r
}

var (
	ErrInvalidYear   = errors.New("invalid year") // "invalid argument"
	ErrInvalidMonth  = errors.New("invalid month")
	ErrInvalidDay    = errors.New("invalid day")
	ErrInvalidHour   = errors.New("invalid hour")
	ErrInvalidMinute = errors.New("invalid minute")
	ErrInvalidSecond = errors.New("invalid second")
)

func String2SysTime(dt string) (time.Time, error) {
	return String2SysTimeLoc(dt, time.Local)
}

func String2SysTimeLoc(dt string, loc *time.Location) (time.Time, error) {
	b := dataTimeStr(dt)
	if len(b) < 4 {
		return time.Time{}, ErrInvalidYear
	}
	y := b.getStringValue(4)
	if y < 0 {
		return time.Time{}, ErrInvalidYear
	}
	if b[0] < '0' || b[0] > '9' {
		b = b[1:]
	}
	m := b.getStringValue(2)
	if m < 1 || m > 12 {
		return time.Time{}, ErrInvalidMonth
	}
	d := 0
	if len(b) > 0 {
		d = b.getStringValue(2)
		if d < 1 || d > 31 {
			return time.Time{}, ErrInvalidDay
		}
	}
	h := 0
	M := 0
	s := 0
	ns := 0
	if len(b) > 0 {
		h = b.getStringValue(2)
		if h < 0 || h > 23 {
			return time.Time{}, ErrInvalidHour
		}
	}
	if len(b) > 0 {
		M = b.getStringValue(2)
		if M < 0 || M > 59 {
			return time.Time{}, ErrInvalidMinute
		}
	}
	if len(b) > 0 {
		s := b.getStringValue(2)
		if s < 0 || s > 59 {
			return time.Time{}, ErrInvalidSecond
		}
	}
	if len(b) > 0 {
		for i, c := range b {
			if i > 8 || c < '0' || c > '9' {
				return time.Time{}, e.ErrInvalid
			}
			n := int(c - '0')
			for j := 8; i < j; j-- {
				n *= 10
			}
			ns += n
		}
	}
	return time.Date(y, time.Month(m), d, h, M, s, ns, loc), nil
}

func SysTime2String(t time.Time) string {
	return t.Format(Layout)
}

func StringToDate(d string) (Date, error) {
	i, err := strconv.Atoi(d)
	return Date(i), err
}

func StringToDatetime(dt string) (Datetime, error) {
	i, err := strconv.ParseUint(dt, 10, 64)
	return Datetime(i), err
}
